setState() in Flutter
setState() is one of the most important concepts in Flutter state management. It is used inside a State object to tell Flutter that some internal state has changed and that the widget's UI may need to be rebuilt.
According to the Flutter API documentation, setState() executes the supplied callback synchronously and then schedules the associated widget to rebuild. If state is changed directly without calling setState(), Flutter may not rebuild the UI to reflect that change.
1. Learning Objectives
After completing this topic, you will understand:
- What
setState() means in Flutter.
- Why
setState() is required.
- How
setState() works internally.
- How to update variables using
setState().
- How to update text, counters, colors, switches, checkboxes, and lists.
- How
setState() works with user interactions.
- How to use
setState() with asynchronous operations.
- Common mistakes when using
setState().
- Best practices for efficient state updates.
2. What is setState()?
setState() is a method provided by Flutter's State class. It is used when a value stored in a StatefulWidget's state changes and the UI needs to respond to that change.
The basic syntax is:
setState(() {
// Change state here
});
For example:
int counter = 0;
setState(() {
counter++;
});
Here, the value of counter changes inside setState(). Flutter is then notified that the widget should be rebuilt.
3. Why Do We Need setState()?
Flutter builds the user interface from the current state of widgets. When a state value changes, Flutter needs to know that the value has changed so that it can rebuild the relevant widget.
For example, suppose we have:
int counter = 0;
If we simply write:
counter++;
the variable changes, but Flutter is not explicitly notified that the UI should rebuild.
Instead, write:
setState(() {
counter++;
});
Now Flutter knows that the state changed and schedules a rebuild.
4. Basic setState() Flow
The basic flow can be understood as:
- User performs an action.
- An event handler is executed.
- The state variable is changed inside
setState().
- Flutter is notified that the state changed.
- The widget's
build() method is scheduled to run again.
- The UI is rebuilt using the new state.
Example:
ElevatedButton(
onPressed: () {
setState(() {
counter++;
});
},
child: const Text('Increase'),
)
When the button is pressed, the counter changes and the UI is rebuilt.
5. StatefulWidget and setState()
setState() is normally used inside a class that extends State.
A StatefulWidget generally contains two classes:
- The
StatefulWidget class.
- The corresponding
State class.
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State createState() => _CounterScreenState();
}
class _CounterScreenState extends State {
int counter = 0;
@override
Widget build(BuildContext context) {
return Text('$counter');
}
}
The mutable variable counter belongs to the State object, and setState() can be used to notify Flutter when that value changes.
6. Simple Counter Example
The counter example is one of the easiest ways to understand setState().
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: CounterScreen(),
);
}
}
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State createState() => _CounterScreenState();
}
class _CounterScreenState extends State {
int counter = 0;
void increaseCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter App'),
),
body: Center(
child: Text(
'$counter',
style: const TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: increaseCounter,
child: const Icon(Icons.add),
),
);
}
}
How it works
counter stores the current value.
- The
Text widget displays the counter.
- The floating action button calls
increaseCounter().
setState() changes counter.
- Flutter rebuilds the widget with the new value.
7. Changing State Without setState()
Consider this code:
void increaseCounter() {
counter++;
}
The Dart variable changes, but the widget is not explicitly notified that the UI should update.
The recommended approach is:
void increaseCounter() {
setState(() {
counter++;
});
}
The state change that affects the UI should be performed inside the setState() callback.
8. setState() and the build() Method
When setState() is called, Flutter schedules the State object for rebuilding. During the rebuild, the build() method reads the updated values.
class ExampleScreen extends StatefulWidget {
const ExampleScreen({super.key});
@override
State createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State {
String message = 'Hello';
void changeMessage() {
setState(() {
message = 'Welcome to Flutter';
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(message),
ElevatedButton(
onPressed: changeMessage,
child: const Text('Change Message'),
),
],
);
}
}
Initially, the UI displays Hello. After the button is pressed, the state changes and the UI displays Welcome to Flutter.
9. Updating Multiple State Variables
Multiple related state variables can be updated in the same setState() call.
int score = 0;
String status = 'Not Started';
void completeQuiz() {
setState(() {
score = 10;
status = 'Completed';
});
}
One state update can therefore cause the UI to reflect several related changes.
10. setState() with Boolean Values
Boolean values are frequently used for showing or hiding UI elements.
bool isVisible = false;
void toggleVisibility() {
setState(() {
isVisible = !isVisible;
});
}
Example UI:
Column(
children: [
if (isVisible)
const Text('This text is visible'),
ElevatedButton(
onPressed: toggleVisibility,
child: const Text('Toggle'),
),
],
)
11. setState() with Switch
bool isDarkMode = false;
Switch(
value: isDarkMode,
onChanged: (value) {
setState(() {
isDarkMode = value;
});
},
)
When the switch changes, the value is updated inside setState().
12. setState() with Checkbox
bool isAccepted = false;
Checkbox(
value: isAccepted,
onChanged: (value) {
setState(() {
isAccepted = value ?? false;
});
},
)
This pattern is useful for terms-and-conditions checkboxes, settings, filters, and selection controls.
13. setState() with TextField
You can use setState() when the text entered by a user needs to immediately affect another part of the UI.
String name = '';
TextField(
onChanged: (value) {
setState(() {
name = value;
});
},
)
Text('Hello $name')
As the user types, the name variable changes and the text widget is rebuilt.
14. setState() with a List
Lists are commonly modified using setState().
List items = ['Apple', 'Banana'];
void addItem() {
setState(() {
items.add('Orange');
});
}
Display the list:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index]),
);
},
)
When an item is added inside setState(), the UI can rebuild and display the new list contents.
15. Adding and Removing Items
List tasks = [];
void addTask(String task) {
setState(() {
tasks.add(task);
});
}
void removeTask(int index) {
setState(() {
tasks.removeAt(index);
});
}
This pattern can be used for:
- To-do applications.
- Shopping carts.
- Notes applications.
- Task management systems.
- Dynamic menus.
16. setState() with Increment and Decrement
int quantity = 1;
void increaseQuantity() {
setState(() {
quantity++;
});
}
void decreaseQuantity() {
if (quantity > 1) {
setState(() {
quantity--;
});
}
}
This is useful in shopping cart interfaces.
17. Complete Shopping Cart Example
class ProductScreen extends StatefulWidget {
const ProductScreen({super.key});
@override
State createState() => _ProductScreenState();
}
class _ProductScreenState extends State {
int quantity = 1;
final double price = 499;
void increaseQuantity() {
setState(() {
quantity++;
});
}
void decreaseQuantity() {
if (quantity > 1) {
setState(() {
quantity--;
});
}
}
@override
Widget build(BuildContext context) {
final double total = price * quantity;
return Scaffold(
appBar: AppBar(
title: const Text('Shopping Cart'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Product Price: ₹$price',
style: const TextStyle(fontSize: 20),
),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: decreaseQuantity,
icon: const Icon(Icons.remove),
),
Text(
'$quantity',
style: const TextStyle(fontSize: 24),
),
IconButton(
onPressed: increaseQuantity,
icon: const Icon(Icons.add),
),
],
),
const SizedBox(height: 20),
Text(
'Total: ₹$total',
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}
Important concept
The total value is calculated during build() using the latest quantity. Whenever quantity changes through setState(), Flutter rebuilds the widget and calculates the new total.
18. setState() with Color Changes
Color boxColor = Colors.blue;
void changeColor() {
setState(() {
boxColor = Colors.green;
});
}
Use the state variable in the UI:
Container(
width: 150,
height: 150,
color: boxColor,
)
When changeColor() is called, the container is rebuilt using the new color.
19. setState() with Conditional UI
setState() is frequently used when the UI should change based on a condition.
bool isLoggedIn = false;
void login() {
setState(() {
isLoggedIn = true;
});
}
UI:
isLoggedIn
? const Text('Welcome User')
: ElevatedButton(
onPressed: login,
child: const Text('Login'),
)
When isLoggedIn becomes true, Flutter rebuilds the widget and displays the appropriate UI.
20. setState() with Loading State
A common real-world use of setState() is displaying loading indicators while an asynchronous operation is running.
bool isLoading = false;
Future loadData() async {
setState(() {
isLoading = true;
});
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
setState(() {
isLoading = false;
});
}
UI example:
isLoading
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: loadData,
child: const Text('Load Data'),
)
The asynchronous work is performed outside the setState() callback. Only the synchronous state changes are placed inside setState().
21. Important Rule: Do Not Make setState() Async
The callback passed to setState() should not be asynchronous.
Incorrect:
setState(() async {
await Future.delayed(const Duration(seconds: 1));
counter++;
});
Correct:
Future updateCounter() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
setState(() {
counter++;
});
}
The Flutter API specifies that the callback passed to setState() must not return a Future.
22. setState() with API Calls
When working with APIs, a common pattern is:
- Set loading state to
true.
- Perform the API request.
- Store the result.
- Set loading state to
false.
- Display the updated data.
bool isLoading = false;
String data = '';
Future fetchData() async {
setState(() {
isLoading = true;
});
await Future.delayed(const Duration(seconds: 2));
final result = 'Data loaded successfully';
if (!mounted) return;
setState(() {
data = result;
isLoading = false;
});
}
23. What is mounted?
The mounted property indicates whether the State object is currently associated with an element in the widget tree.
It is particularly useful after asynchronous operations.
Future loadData() async {
await Future.delayed(const Duration(seconds: 2));
if (!mounted) {
return;
}
setState(() {
data = 'Loaded';
});
}
This prevents attempting to call setState() on a State object that has already been disposed.
24. setState() After dispose()
Calling setState() after a State object has been disposed is an error.
This can happen when:
- A timer continues running after the screen is closed.
- An asynchronous operation completes after the widget is removed.
- An animation callback continues after disposal.
- A stream subscription continues after disposal.
Whenever possible, cancel the work that can trigger the update during dispose(). Checking mounted can also protect an update after asynchronous work.
25. setState() and Timer
Timer? timer;
int seconds = 0;
@override
void initState() {
super.initState();
timer = Timer.periodic(
const Duration(seconds: 1),
(_) {
if (!mounted) return;
setState(() {
seconds++;
});
},
);
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
Remember to import:
import 'dart:async';
26. Updating State Before and After an Operation
For a loading operation, state changes can be separated from the actual work.
Future submitForm() async {
setState(() {
isLoading = true;
});
final result = await submitData();
if (!mounted) return;
setState(() {
isLoading = false;
message = result;
});
}
This structure keeps the setState() callbacks small and focused on actual state changes.
27. Keep setState() Small
It is good practice to place only the state-changing operation inside setState().
Instead of:
setState(() {
performLargeCalculation();
saveData();
updateCounter();
counter++;
});
Prefer:
performLargeCalculation();
saveData();
setState(() {
counter++;
});
This makes the code easier to understand and keeps the purpose of setState() clear.
28. setState() Should Only Be Used When UI Needs to React
If a value changes but that change does not affect the widget's output, rebuilding the UI may not be necessary.
For example:
int calculationResult = 0;
void calculate() {
calculationResult = 100;
}
If calculationResult is not displayed or used by the widget's build logic, calling setState() may be unnecessary.
29. setState() and Performance
Calling setState() schedules a rebuild of the associated State object and potentially its descendant subtree. Therefore, state updates should be kept meaningful and reasonably localized.
Good practices include:
- Call
setState() only when the UI needs to react.
- Avoid redundant calls.
- Keep frequently changing state close to the UI that uses it when practical.
- Split large widgets into smaller widgets.
- Use
const constructors where possible.
- Avoid putting expensive calculations directly inside frequently executed build methods.
30. Multiple setState() Calls
It is usually cleaner to combine related state changes.
Instead of:
setState(() {
firstName = 'John';
});
setState(() {
lastName = 'Smith';
});
You can often write:
setState(() {
firstName = 'John';
lastName = 'Smith';
});
This communicates that the changes belong to the same UI update.
31. setState() and User Interaction
Many Flutter widgets provide callbacks where setState() can be used.
| Widget |
Common Callback |
Example State |
| ElevatedButton |
onPressed |
Counter, form submission |
| Switch |
onChanged |
Enable/disable setting |
| Checkbox |
onChanged |
Selection |
| Slider |
onChanged |
Numeric value |
| TextField |
onChanged |
Input text |
| DropdownButton |
onChanged |
Selected option |
32. Slider Example
double volume = 50;
Slider(
value: volume,
min: 0,
max: 100,
onChanged: (value) {
setState(() {
volume = value;
});
},
)
The slider value changes continuously, and the UI can react to the updated state.
33. Dropdown Example
String selectedCity = 'Mumbai';
DropdownButton(
value: selectedCity,
items: const [
DropdownMenuItem(
value: 'Mumbai',
child: Text('Mumbai'),
),
DropdownMenuItem(
value: 'Delhi',
child: Text('Delhi'),
),
DropdownMenuItem(
value: 'Pune',
child: Text('Pune'),
),
],
onChanged: (value) {
if (value == null) return;
setState(() {
selectedCity = value;
});
},
)
34. Favorite Button Example
bool isFavorite = false;
IconButton(
onPressed: () {
setState(() {
isFavorite = !isFavorite;
});
},
icon: Icon(
isFavorite
? Icons.favorite
: Icons.favorite_border,
),
)
This is a simple example of changing a boolean state and dynamically changing an icon.
35. Password Visibility Example
bool obscurePassword = true;
TextField(
obscureText: obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
obscurePassword = !obscurePassword;
});
},
),
),
)
This pattern is commonly used in login and registration screens.
36. Complete Login UI Example
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State createState() => _LoginScreenState();
}
class _LoginScreenState extends State {
bool obscurePassword = true;
bool isLoading = false;
Future login() async {
setState(() {
isLoading = true;
});
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
setState(() {
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Login completed'),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Login'),
),
body: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
TextField(
decoration: const InputDecoration(
labelText: 'Email',
),
),
const SizedBox(height: 16),
TextField(
obscureText: obscurePassword,
decoration: InputDecoration(
labelText: 'Password',
suffixIcon: IconButton(
icon: Icon(
obscurePassword
? Icons.visibility
: Icons.visibility_off,
),
onPressed: () {
setState(() {
obscurePassword = !obscurePassword;
});
},
),
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: isLoading ? null : login,
child: isLoading
? const CircularProgressIndicator()
: const Text('Login'),
),
],
),
),
);
}
}
37. Common Mistakes with setState()
Mistake 1: Changing UI State Without setState()
counter++;
When the changed value affects the UI, use:
setState(() {
counter++;
});
Mistake 2: Making setState() Callback Async
setState(() async {
await someFunction();
});
Instead, perform asynchronous work outside setState() and update the state synchronously afterward.
Mistake 3: Calling setState() After dispose()
Async callbacks, timers, animations, and subscriptions should be handled carefully so they do not update a disposed State object.
Mistake 4: Calling setState() Unnecessarily
Do not use setState() simply because a value changed. Use it when the change needs to affect the widget's UI.
Mistake 5: Performing Heavy Work Inside setState()
Keep expensive calculations, network requests, and database operations outside the setState() callback.
38. Correct Pattern for Async State Updates
Future fetchUser() async {
setState(() {
isLoading = true;
});
final user = await getUser();
if (!mounted) return;
setState(() {
username = user.name;
isLoading = false;
});
}
The important idea is that setState() contains only the synchronous state modifications.
39. setState() vs Direct Variable Assignment
| Direct Assignment |
setState() |
| Changes the Dart variable. |
Changes the state and notifies Flutter. |
| Does not itself schedule a widget rebuild. |
Schedules the State object for rebuilding. |
| Useful when UI does not need to react. |
Useful when UI needs to reflect the state change. |
40. setState() vs StatefulWidget
StatefulWidget provides a structure for widgets that have mutable state, while setState() tells Flutter that a change in that State may require the UI to rebuild.
StatefulWidget
↓
State object
↓
State variables
↓
setState()
↓
build()
↓
Updated UI
41. setState() and Widget Tree
Suppose the widget tree contains:
Scaffold
├── AppBar
└── Column
├── Text
└── ElevatedButton
If the State object associated with the screen calls setState(), Flutter schedules that State for rebuilding. The build method produces the updated widget configuration for the relevant subtree.
42. Example: Simple Score Application
class ScoreScreen extends StatefulWidget {
const ScoreScreen({super.key});
@override
State createState() => _ScoreScreenState();
}
class _ScoreScreenState extends State {
int score = 0;
void addPoint() {
setState(() {
score++;
});
}
void resetScore() {
setState(() {
score = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Score App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Score: $score',
style: const TextStyle(fontSize: 30),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: addPoint,
child: const Text('Add Point'),
),
ElevatedButton(
onPressed: resetScore,
child: const Text('Reset'),
),
],
),
),
);
}
}
43. Local State and setState()
setState() is particularly suitable for small, local pieces of state that belong to one widget.
Examples include:
- Whether a password is visible.
- Whether a switch is enabled.
- Current counter value.
- Selected tab.
- Selected dropdown value.
- Temporary form UI state.
- Loading indicator state.
44. When setState() May Not Be Enough
For small local state, setState() is often straightforward. Larger applications may need more structured state-management approaches when state must be shared across many unrelated widgets or features.
Common approaches in Flutter projects include:
- Provider
- Riverpod
- Bloc/Cubit
- GetX
- Other application-specific state-management architectures
The appropriate approach depends on application size, architecture, state-sharing requirements, and team preferences.
45. setState() Best Practices
- Use
setState() when a state change affects the UI.
- Keep the callback short and focused.
- Do not make the callback
async.
- Perform API calls and other asynchronous work outside the callback.
- Check
mounted before updating state after asynchronous work when necessary.
- Cancel timers, subscriptions, and other ongoing work in
dispose() where appropriate.
- Avoid redundant calls to
setState().
- Keep state close to the widgets that use it when practical.
- Use
const widgets where possible.
- Split large widgets into smaller components when appropriate.
46. Real-World Example: To-Do App State
class TodoScreen extends StatefulWidget {
const TodoScreen({super.key});
@override
State createState() => _TodoScreenState();
}
class _TodoScreenState extends State {
final List tasks = [];
void addTask(String task) {
if (task.trim().isEmpty) return;
setState(() {
tasks.add(task);
});
}
void deleteTask(int index) {
setState(() {
tasks.removeAt(index);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todo App'),
),
body: ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(tasks[index]),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
deleteTask(index);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
addTask('Learn Flutter');
},
child: const Icon(Icons.add),
),
);
}
}
Here, adding and deleting tasks are state changes. Both operations use setState() so the task list can be rebuilt with the latest data.
47. Understanding the Complete State Update Cycle
User Action
↓
Event Callback
↓
setState()
↓
State Variable Changes
↓
Flutter Schedules Build
↓
build() Executes
↓
New Widget Configuration
↓
Updated UI
This cycle is fundamental to understanding interactive Flutter applications.
48. Important API Facts
setState() is a method of the State class.
- Its callback is executed synchronously.
- The callback must not return a
Future.
- Calling it tells Flutter that internal state has changed and the widget may need to rebuild.
- Calling it after
dispose() is an error.
- Redundant calls should be avoided because rebuilding a widget subtree has an indirect performance cost.
49. Official Flutter Resources
50. Flutter Training Resource
For structured Flutter learning covering Dart, widgets, state management, APIs, Firebase, projects, and other Flutter development topics, visit the following course resource:
JustAcademy Flutter Training
For course/demo registration:
Register for Flutter Course Demo
51. Interview Questions on setState()
Q1. What is setState() in Flutter?
setState() is a method of the State class used to notify Flutter that internal state has changed and that the widget may need to rebuild.
Q2. Why is setState() required?
It tells Flutter that a state change may affect the UI and schedules the associated State object for rebuilding.
Q3. Can setState() be async?
No. The callback passed to setState() must not return a Future. Perform asynchronous work outside the callback and update the state synchronously afterward.
Q4. What happens when setState() is called?
The callback is executed synchronously, the State object is marked for rebuilding, and Flutter later calls the relevant build process to update the UI.
Q5. Can setState() be called after dispose()?
No. Calling setState() after disposal is an error.
Q6. What is mounted?
mounted indicates whether the State object is currently associated with an element in the widget tree.
Q7. Can multiple variables be changed inside one setState()?
Yes. Related state changes can be grouped inside the same setState() callback.
Q8. Is setState() a global state-management solution?
No. It is primarily a mechanism for notifying Flutter about state changes in a particular State object. Larger applications may use additional state-management approaches for shared or complex state.
52. Practice Exercises
- Create a counter app with Increase, Decrease, and Reset buttons.
- Create a dark-mode switch using
setState().
- Create a password field with show/hide functionality.
- Create a shopping cart with quantity increase and decrease buttons.
- Create a to-do list where users can add and delete tasks.
- Create a checkbox-based terms and conditions UI.
- Create a slider that displays its current numeric value.
- Create a loading button that displays a progress indicator during an asynchronous operation.
- Create a favorite button that changes between favorite and unfavorite icons.
- Create a dropdown that updates the selected city on the screen.
53. Quick Revision
| Concept |
Meaning |
| State |
Information that can change during a widget's lifetime. |
| StatefulWidget |
A widget whose associated State can change during its lifetime. |
| State |
Object that stores mutable state and contains the build logic. |
| setState() |
Notifies Flutter that the State has changed and may need rebuilding. |
| build() |
Produces the widget configuration based on the current state. |
| mounted |
Indicates whether the State is currently mounted in the widget tree. |
| dispose() |
Used for cleanup when the State is permanently removed. |
54. Key Takeaways
setState() is fundamental for managing simple local state in Flutter.
- State changes that affect the UI should generally be performed inside
setState().
- Calling
setState() notifies Flutter that the associated State may need to rebuild.
- The callback passed to
setState() should be synchronous.
- Never put an
await operation directly inside the setState() callback.
- For asynchronous operations, perform the work first and then update state inside a synchronous
setState() call.
- Use
mounted when necessary to avoid updating a State object after it has been removed from the widget tree.
- Cancel timers, subscriptions, and other ongoing work during
dispose() when appropriate.
- Avoid unnecessary
setState() calls because they can cause additional widget rebuilding.
- For larger applications, more structured state-management solutions may be appropriate.
Conclusion
setState() is a core Flutter mechanism for creating interactive user interfaces. Whenever a StatefulWidget's local state changes and that change should be reflected in the UI, setState() provides the notification Flutter needs to rebuild the relevant widget. Understanding the relationship between state variables, setState(), and build() is essential before moving on to advanced state-management approaches such as Provider, Riverpod, Bloc, or GetX.